package park.borne;

import park.*;

/**
 *  Cette classe gère la borne
 *  - Les communications avec les E/S se font via le bus I2C
 *  - Les communications avec le superviseur se font via la classe parent AccesParking
 */

public final class Borne implements Runnable, ScanListener
{
      //constantes caracteristiques de la borne:
      private static final int DUREE_WATCHDOG = 30000/50;  //
      private static final int NOMBRE_ESSAI_MAX = 3;

      // definition des messages aparaissant sur l'afficheur
      private static final String MESSAGE_INIT_1              = "-   Bonjour!   -";
      private static final String MESSAGE_INIT_2              = "-  **********  -";

      private static final String MESSAGE_ENTREZ              = " entrez...      ";
      private static final String MESSAGE_ACCES_REFUSE        = " Acces refuse ! ";
      private static final String MESSAGE_ACCES_AUTORISE      = " acces autorise ";
      private static final String MESSAGE_CODE_NON_VALIDE     = "code non valide ";
      private static final String MESSAGE_3_ESSAIS_MAX        = "- " + NOMBRE_ESSAI_MAX + " essais max -";
      private static final String MESSAGE_RETIRER_CARTE       = " retirer carte  ";
      private static final String MESSAGE_PARKING_PLEIN       = " -- COMPLET --  ";
      private static final String MESSAGE_HORS_SERVICE        = "  hors service  ";
      private static final String MESSAGE_DEFAUT              = "    en defaut   ";
      private static final String MESSAGE_NO_CHANGE           = "";

      // messages destines au superviseur:
      public static final String CODE_DEFAUT_BARRIERE_BLOQUE  = "barrière bloquée";
      public static final String CODE_DEFAUT_CARTE_OUBLIEE    = "carte oubliée";
      public static final String CODE_DEFAUT_DOUBLE_ENTREE    = "entrée de 2 véhicules";
      public static final String CODE_DEFAUT_TEST             = "test défaut";
      public static final String CODE_3_ESSAIS                = NOMBRE_ESSAI_MAX + " Essais";

     //les types d'evenements:
      private static final byte EV_SOURCE_CLAVIER_CHIFFRE          = (byte)0x00;
      private static final byte EV_SOURCE_CLAVIER_EXT              = (byte)0x10;
      private static final byte EV_SOURCE_BARRIERE                 = (byte)0x20;
      private static final byte EV_SOURCE_CAP                      = (byte)0x30;
      private static final byte EV_SOURCE_SUPERVISEUR              = (byte)0x40;
      private static final byte EV_SOURCE_BORNE                    = (byte)0x60;

    // evenements recus du clavier:
      private static final byte EV_CLAVIER_TOUCHE_ETOILE           = (byte)0x10;
      private static final byte EV_CLAVIER_TOUCHE_APPEL_GARDIEN    = (byte)0x13;

      // evenements recus de la barriere:
      public static final byte EV_VEHICULE_PRESENT_ENTREE          = (byte)0x20;
      public static final byte EV_VEHICULE_ENTRE                   = (byte)0x21;
      public static final byte EV_VEHICULE_SORT                    = (byte)0x22;
      public static final byte EV_VEHICULE_ENTREE_AVORTEE          = (byte)0x23;
      public static final byte EV_DEFAUT_BARRIERE_BLOQUE           = (byte)0x24;

      //evenements recus de la carte a puce:
      public static final byte EV_CARTE_INTRODUITE                 = (byte)0x30;
      public static final byte EV_CARTE_RETIREE                    = (byte)0x31;

      // evenements recus du superviseur:
      private static final byte EV_PARKING_PLEIN_ON                = (byte)0x40;
      private static final byte EV_PARKING_PLEIN_OFF               = (byte)0x41;
      private static final byte EV_AUTORISATION_GARDIEN            = (byte)0x42;
      private static final byte EV_ALARME_ON                       = (byte)0x43;
      private static final byte EV_ALARME_OFF                      = (byte)0x44;

      // evenements recus de moi meme:

      private static final byte EV_DEFAUT_WATCHDOG_ON              = (byte)0x60;
      private static final byte EV_DEFAUT_WATCHDOG_OFF             = (byte)0x61;

      private static final byte EV_DUMMY                           = (byte)0x70;
      private static final byte NO_EV                              = (byte)0xFF;

      // codes evenements vehicules destines au superviseur
      public static final int CODE_EV_VEHICULE_ENTRE               = 0x01;
      public static final int CODE_EV_VEHICULE_SORT                = 0x02;
      public static final int CODE_EV_VEHICULE_AUT_OFF             = 0x04;



      /**
       *   barriere avec son moteur et ses capteurs
       */
      private Barriere barriere;

      /**
       *  clavier (+ un bouton poussoir appel gardien)
       *  clavier matrice 3x4 mais matrice 4x4 (appel gardien)
       *      => 3 cellules non utilisees
       */
      private Clavier clavier;

      /**
       * afficheur LCD 2 lignes de 16 caracteres
       * il gere aussi le code d'acces
       */
      private Afficheur afficheur;

      /**
       * lecteur de cartes a puce
       * - detection presence carte
       * - alimentation carte
       * - lecture/programmation carte
       */
      private CarteAP carteAP;

      /**
       * pour scruter les entrees (bus I2C)
       * Interruptions non utilisees
       * => detection des entrees par scrutation toutes les 40 ms
       */
      private ScanThread scanThread;

      /**
       *  reference sur la classe mere
       *  pour communication avec superviseur
       *  AccesParking fait office de noeud de communication
       */
      private AccesParking accesParking;

      /**
       * Buzzer
       * utilise pour avertir automobiliste
       */
      private Klaxon klaxon;

      /**
       *  implantation d'une machine d'etat
       *  gere tous les evenements
       */
      private ControleurBorne controleurBorne;

      /**
       * gere la file d'attente des evenements
       * producteurs: clavier, barriere, timer
       * consommateur: borne
       */
      private  TinyEventQueue tinyEventQueue;

      /**
       *  pour arreter le thread
       */
      private volatile boolean stopBorne;

      /**
       *  pour la securite
       *  permet de placer la borne en defaut (ouverte) si superviseur defaillant
       */
      private volatile int watchDog;


// *****************************************************************************
//                                 METHODES
// *****************************************************************************

      /**
      * Constructeur du poste barriere
      * @param accesParking AccesParking gerant la borne
      * @throws Exception si erreur creation composants I2c
      */
      public Borne(final AccesParking accesParking) throws Exception
      {
	 this.accesParking = accesParking;

	 controleurBorne = new ControleurBorne();
	 tinyEventQueue = new TinyEventQueue();

	 afficheur = new Afficheur();
	 clavier = new Clavier(this);
	 barriere  = new Barriere(this);
	 carteAP = new CarteAP(this);
	 klaxon = new Klaxon();

	 scanThread = new ScanThread();
	 scanThread.addToScan(clavier);
	 scanThread.addToScan(barriere);
	 scanThread.addToScan(carteAP);
	 scanThread.addToScan(klaxon);
	 scanThread.addToScan(this);

	 afficheur.initAfficheur();
	 afficheur.afficheMessages(MESSAGE_INIT_1, MESSAGE_INIT_2);
      }

      /**
       * demarrage des 2 flots d'execution
       * Tous les Threads ont la meme priorite pour les TINIs API 1.02
       * On peut gerer la priorite des threads pour les TINIs API 1.1x
       */
      public final void start()
      {
	 Thread myThread = new Thread(this);
	 //myThread.setPriority(10);
	 myThread.start();
	 scanThread.start();
      }

      /**
       * detruire la borne
       */
      public final void stop()
      {
	 scanThread.stop(); // fin du Thread "scrutation des entrees"
	 afficheur.afficheMessages(MESSAGE_HORS_SERVICE, MESSAGE_INIT_2);
	 stopBorne = true;
	 afficheur.kill();
	 clavier.kill();
	 barriere.kill();
	 carteAP.kill();
	 klaxon.kill();
	 putEvent(EV_DUMMY);  // fin du Thread "traitement evenements"
      }


      /**
       * gestion de la barriere
       * traite les evenements issus de la file d'attente
       */
      public final void run()
      {
	 byte event;
	 while (!stopBorne)
	 {
	     while ((event = (byte)getEvent()) !=  NO_EV) controleurBorne.process(event);
	 }
	 System.out.println("fin du thread Evenements");
      }

      public final void doScan()
      {
	if (watchDog > 0)                     // surveillance superviseur actif
	{
	  if (watchDog == DUREE_WATCHDOG)
	  {
	     putEvent(EV_DEFAUT_WATCHDOG_OFF);
	  }
	   watchDog--;
	   if (watchDog == 0)
	   {
	      putEvent(EV_DEFAUT_WATCHDOG_ON);
	   }
	}
      }

      public final void razWatchDog()
      {
	  watchDog = DUREE_WATCHDOG;
      }

      // appelée par superviseur, barriere, clavier et timer quand un evenement se produit
      public final void putEvent(final byte code)
     {
	 synchronized(tinyEventQueue)
	 {
	    tinyEventQueue.write(code);
	    tinyEventQueue.notify();
	 }
     }

     private final byte getEvent()
     {
	byte result;
	synchronized(tinyEventQueue)
	{
	    result = ((byte)tinyEventQueue.read());
	    if (result == -1) // plus d'evenements dans la queue
	    try
	    {
		tinyEventQueue.wait();
	    }
	    catch (InterruptedException e) {}
	}
	return result;
      }

     private final boolean isCodeAccesCarteValide()
     {
	 String code = carteAP.codeCarteAP(false, "");
	 //System.out.println("code lu: " + code);
	 return accesParking.isAutorise(true, code);
     }

     private final boolean isCodeAccesClavierValide()
     {
	 String code = afficheur.getCodeClavier();
	 return  accesParking.isAutorise(false,code);
     }


//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//                           gestion des evenements
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

     /**
      * <p>Titre :controleur de la borne </p>
      * <p>Description : machine d'etat de la borne</p>
      * <p>Copyright : Copyright (c) 2003</p>
      * <p>Société : </p>
      * @author non attribué
      * @version 1.0
      */

     private final class ControleurBorne
     {
	 // les etats:
	 private static final byte ETAT_ALARME                     = 0;
	 private static final byte ETAT_DEFAUT                     = 1;
	 private static final byte ETAT_PARKING_PLEIN              = 2;
	 private static final byte ETAT_ATTENTE_VEHICULE           = 3;
	 private static final byte ETAT_ATTENTE_CODE               = 4;
	 private static final byte ETAT_CARTE_INTRODUITE           = 5;

	 private byte etatCourant = ETAT_ATTENTE_VEHICULE ;
	 private boolean codeValide = false;
	 private int nbreEssais;
	 private boolean autorisationGardien;
	 private boolean parkingPlein;
	 private byte evType;

	 byte getEtatCourant()
	 {
	     return etatCourant;
	 }

	 // structure adoptee pour optimisation duree d'execution
	 void process(final byte evenement)
	 {
	     //System.out.println("etat borne: " + Integer.toHexString(evenement));
	     evType = (byte)(evenement & 0xf0);
	     switch (evType)
	     {
	     //*****************************************************************
		 case EV_SOURCE_BORNE:
		     switch(evenement)
		     {
			 case EV_DEFAUT_WATCHDOG_ON:
			     if (etatCourant != ETAT_ALARME)
			     {
				 etatCourant = ETAT_DEFAUT;
				 afficheur.stopModeEdit();
				 barriere.setManuel(true);
				 afficheur.afficheMessages(MESSAGE_DEFAUT, MESSAGE_INIT_2);
			     }
			     break;
			 case EV_DEFAUT_WATCHDOG_OFF:
			     if (etatCourant == ETAT_DEFAUT)
			     {
				 if (parkingPlein)
				 {
				     etatCourant = ETAT_PARKING_PLEIN;
				     afficheur.afficheMessages(MESSAGE_PARKING_PLEIN, MESSAGE_INIT_2);
				 }
				 else
				 {
				     etatCourant = ETAT_ATTENTE_VEHICULE;
				     afficheur.afficheMessages(MESSAGE_INIT_1, MESSAGE_INIT_2);
				 }
				 barriere.setManuel(false);
			     }
			     break;
		     }
		     break;

		//**************************************************************

		 case EV_SOURCE_SUPERVISEUR:
		     switch(evenement)
		     {
			 case EV_ALARME_ON:

			     barriere.setManuel(true);
			     afficheur.stopModeEdit();
			     afficheur.afficheMessages(MESSAGE_HORS_SERVICE, MESSAGE_INIT_2);
			     etatCourant = ETAT_ALARME;
			     break;

			 case EV_ALARME_OFF:

			     if (etatCourant == ETAT_ALARME)
			     {
				 if (parkingPlein)
				 {
				     etatCourant = ETAT_PARKING_PLEIN;
				     afficheur.afficheMessages(MESSAGE_PARKING_PLEIN, MESSAGE_INIT_2);
				 }
				 else
				 {
				     etatCourant = ETAT_ATTENTE_VEHICULE;
				     afficheur.afficheMessages(MESSAGE_INIT_1, MESSAGE_INIT_2);
				 }
				 barriere.setManuel(false);
			     }
			     break;

			 case EV_PARKING_PLEIN_ON:                              //parking plein

			     parkingPlein = true;
			     if (etatCourant != ETAT_ALARME)
			     {                                                  // mise en mode parking plein
				 etatCourant = ETAT_PARKING_PLEIN;
				 afficheur.stopModeEdit();
				 afficheur.afficheMessages(MESSAGE_PARKING_PLEIN, MESSAGE_INIT_2);
			     }
			     break;

			 case EV_PARKING_PLEIN_OFF :                            // parking plus plein

			     parkingPlein = false;
			     if (etatCourant != ETAT_ALARME)
			     {
				 etatCourant = ETAT_ATTENTE_VEHICULE;
				 afficheur.afficheMessages(MESSAGE_INIT_1, MESSAGE_INIT_2);
			     }
			     break;

			 case EV_AUTORISATION_GARDIEN:                          // autorisation passage par gardien

			     if (etatCourant == ETAT_ATTENTE_CODE)
			     {
				 afficheur.stopModeEdit();
				 afficheur.afficheMessages(MESSAGE_ACCES_AUTORISE, MESSAGE_ENTREZ);
				 barriere.autorise(true);
				 //accesParking.evVehicule(CODE_EV_VEHICULE_AUT_OFF);
				 etatCourant = ETAT_ATTENTE_VEHICULE;
			     }
			     else
			      if (etatCourant == ETAT_CARTE_INTRODUITE)
			      {
				 autorisationGardien = true;
				 afficheur.afficheMessages(MESSAGE_ACCES_AUTORISE, MESSAGE_RETIRER_CARTE);
			      }
			     break;
		     }
		     break;

		//**************************************************************

		 case EV_SOURCE_BARRIERE:
		     switch(evenement)
		     {
			 case EV_VEHICULE_PRESENT_ENTREE:                         // vehicule present entree
			     if(etatCourant == ETAT_ATTENTE_VEHICULE)
			     {
				 if (autorisationGardien)
				 {
				     barriere.autorise(true);
				     autorisationGardien = false;
				     //accesParking.evVehicule(CODE_EV_VEHICULE_AUT_OFF);
				 }
				 else
				 {
				     etatCourant = ETAT_ATTENTE_CODE;
				     afficheur.startModeEdit();
				 }
				 nbreEssais = 0;
			     }
			     break;

			 case EV_VEHICULE_ENTRE:                                         // vehicule entre dans le parking
			     if (etatCourant == ETAT_ATTENTE_VEHICULE)
			     {
				 afficheur.afficheMessages(MESSAGE_INIT_1, MESSAGE_INIT_2);
				 accesParking.evVehicule(CODE_EV_VEHICULE_ENTRE);         // envoi au superviseur du message: vehicule entre.
			     }
			     break;

			 case EV_VEHICULE_SORT:                                          // vehicule sort du parking

			     if ((etatCourant == ETAT_ATTENTE_VEHICULE) | (etatCourant == ETAT_PARKING_PLEIN))
			     {
				 accesParking.evVehicule(CODE_EV_VEHICULE_SORT);           // envoi au superviseur du message: vehicule sort.
			     }
			     break;

			 case EV_VEHICULE_ENTREE_AVORTEE:                                // vehicule quitte boucle amont sans entrer !!
			     if ((etatCourant == ETAT_ATTENTE_CODE) |  (etatCourant == ETAT_CARTE_INTRODUITE) | (etatCourant == ETAT_ATTENTE_VEHICULE))
			     {
				 afficheur.stopModeEdit();
				 if (etatCourant == ETAT_CARTE_INTRODUITE)
				 {
				     klaxon.doKlaxon();
				     accesParking.defaut(CODE_DEFAUT_CARTE_OUBLIEE);
				 }
				 etatCourant = ETAT_ATTENTE_VEHICULE;
				 autorisationGardien = false;
				 afficheur.afficheMessages(MESSAGE_INIT_1, MESSAGE_INIT_2);
			     }
			     break;

			 case EV_DEFAUT_BARRIERE_BLOQUE:  //timer
			     accesParking.defaut(CODE_DEFAUT_BARRIERE_BLOQUE);
			     break;
		     }
		     break;

		//**************************************************************

		 case EV_SOURCE_CAP:
		     switch(evenement)
		     {
			 case EV_CARTE_INTRODUITE:                                       // carte introduite

			     if (etatCourant == ETAT_ATTENTE_CODE)
			     {
				 codeValide = isCodeAccesCarteValide();
				 afficheur.stopModeEdit();
				 if (codeValide) afficheur.afficheMessages(MESSAGE_ACCES_AUTORISE, MESSAGE_RETIRER_CARTE);
				 else afficheur.afficheMessages(MESSAGE_ACCES_REFUSE, MESSAGE_RETIRER_CARTE);
				 etatCourant = ETAT_CARTE_INTRODUITE;
			     }
			     break;

			 case EV_CARTE_RETIREE:                                          // carte retiree

			     if (etatCourant == ETAT_CARTE_INTRODUITE)
			     {
				 if (autorisationGardien)
				 {
				     codeValide = true;
				     autorisationGardien = false;
				 }
				 barriere.autorise(codeValide);
				 if (codeValide)
				 {
				     afficheur.afficheMessages(MESSAGE_NO_CHANGE, MESSAGE_ENTREZ);
				     etatCourant = ETAT_ATTENTE_VEHICULE;
				 }
				 else
				 {
				     afficheur.afficheMessages(MESSAGE_CODE_NON_VALIDE, MESSAGE_NO_CHANGE);
				     afficheur.startModeEdit();
				     etatCourant = ETAT_ATTENTE_CODE;
				 }
			     }
			     break;
		     }
		     break;

		//**************************************************************

		 case  EV_SOURCE_CLAVIER_EXT :

		     switch(evenement)
		     {
			 case EV_CLAVIER_TOUCHE_APPEL_GARDIEN:                  // appel gardien
			     accesParking.appelGardien();
			     break;

			 case EV_CLAVIER_TOUCHE_ETOILE:                         // touche * : simulation alarme
			     accesParking.defaut(CODE_DEFAUT_TEST);
			     break;
		     }
		     break;

		 //*************************************************************

		 case EV_SOURCE_CLAVIER_CHIFFRE:                                // une touche clavier 0..9 ou # enfoncee

		     if(etatCourant == ETAT_ATTENTE_CODE)
		     {
			 afficheur.doTouche(evenement);
			 if (afficheur.isCodeAccesComplet())
			 {
			    codeValide = isCodeAccesClavierValide();
			    if (codeValide)
			    {
				afficheur.stopModeEdit();
				afficheur.afficheMessages(MESSAGE_ACCES_AUTORISE, MESSAGE_ENTREZ);
				barriere.autorise(true);
				etatCourant = ETAT_ATTENTE_VEHICULE;
			    }
			    else
			    {
			       if (++nbreEssais < NOMBRE_ESSAI_MAX)
			       {
				  afficheur.afficheMessages(MESSAGE_CODE_NON_VALIDE, MESSAGE_NO_CHANGE);
				  afficheur.startModeEdit();

				}
				else
				{
				    afficheur.stopModeEdit();
				    afficheur.afficheMessages( MESSAGE_ACCES_REFUSE, MESSAGE_3_ESSAIS_MAX);
				    accesParking.defaut(CODE_3_ESSAIS);
				    etatCourant = ETAT_ATTENTE_VEHICULE;
				}
			     }
			  }
		      }
		      break;

		 //*************************************************************
	     }
	 }
     }



// *****************************************************************************
// **                     Messages provenant du superviseur                   **
// *****************************************************************************

     /**
      * @param on boolean true si mise en alarme
      */
     public final void setAlarme(boolean on)
     {
	putEvent(on ? EV_ALARME_ON : EV_ALARME_OFF);
     }

     public final void setAutorisation()
     {
       putEvent(EV_AUTORISATION_GARDIEN);
     }

     public final void parkingPlein(boolean plein)
     {
	putEvent(plein ? EV_PARKING_PLEIN_ON : EV_PARKING_PLEIN_OFF);
     }

//******************************************************************************
     // les methodes suivantes sont executees d'une maniere asynchrone par rapport
     // a la queue des evenements (pas dans le meme Thread)

     /**
      * affichage d'un message
      * @param message String la chaine à afficher
      */
     public final void afficheMessage( final String message)
     {
	 afficheur.afficheMessageSuperviseur(message);
     }

     public final boolean eclaireAfficheur(boolean modif, boolean eclaire)
     {
	 return afficheur.setEclairage(modif, eclaire);
     }

     public final String codeCarteAP(boolean write, final String code)
     {
	 return  carteAP.codeCarteAP(write, code);
     }


}
